/*-------------------------------------------------------------------------
 *
 * lockcmds.c
 *	  锁定命令支持代码
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/lockcmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/table.h"
#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/pg_inherits.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteHandler.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

static void fc_LockTableRecurse(Oid fc_reloid, LOCKMODE fc_lockmode, bool fc_nowait);
static AclResult fc_LockTableAclCheck(Oid fc_relid, LOCKMODE fc_lockmode, Oid fc_userid);
static void fc_RangeVarCallbackForLockTable(const RangeVar *fc_rv, Oid fc_relid,
										 Oid fc_oldrelid, void *fc_arg);
static void fc_LockViewRecurse(Oid fc_reloid, LOCKMODE fc_lockmode, bool fc_nowait,
							List *fc_ancestor_views);

/*
 * 锁定表
 */
void LockTableCommand(LockStmt *fc_lockstmt)
{
	ListCell   *fc_p;

	/*
	 * 遍历列表并逐一处理命名关系
	 */
	foreach(fc_p, fc_lockstmt->relations)
	{
		RangeVar   *fc_rv = (RangeVar *) lfirst(fc_p);
		bool		fc_recurse = fc_rv->inh;
		Oid			fc_reloid;

		fc_reloid = RangeVarGetRelidExtended(fc_rv, fc_lockstmt->mode,
										  fc_lockstmt->nowait ? RVR_NOWAIT : 0,
										  fc_RangeVarCallbackForLockTable,
										  (void *) &fc_lockstmt->mode);

		if (get_rel_relkind(fc_reloid) == RELKIND_VIEW)
			fc_LockViewRecurse(fc_reloid, fc_lockstmt->mode, fc_lockstmt->nowait, NIL);
		else if (fc_recurse)
			fc_LockTableRecurse(fc_reloid, fc_lockstmt->mode, fc_lockstmt->nowait);
	}
}

/*
 * 在获取命名表的表锁之前，检查我们是否有权限这样做。
 */
static void fc_RangeVarCallbackForLockTable(const RangeVar *fc_rv, Oid fc_relid, Oid fc_oldrelid,
							 void *fc_arg)
{
	LOCKMODE	fc_lockmode = *(LOCKMODE *) fc_arg;
	char		fc_relkind;
	char		fc_relpersistence;
	AclResult	fc_aclresult;

	if (!OidIsValid(fc_relid))
		return;					/* 不存在，因此不进行权限检查 */
	fc_relkind = get_rel_relkind(fc_relid);
	if (!fc_relkind)
		return;					/* 哎呀，已被同时删除；不进行权限检查 */

	/* 目前，我们只允许锁定普通表或视图 */
	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE &&
		fc_relkind != RELKIND_VIEW)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot lock relation \"%s\"",
						fc_rv->relname),
				 errdetail_relkind_not_supported(fc_relkind)));

	/*
	 * 如果在此事务中访问了临时关系，请做记下。
	 */
	fc_relpersistence = get_rel_persistence(fc_relid);
	if (fc_relpersistence == RELPERSISTENCE_TEMP)
		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;

	/* 检查权限。 */
	fc_aclresult = fc_LockTableAclCheck(fc_relid, fc_lockmode, GetUserId());
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, get_relkind_objtype(get_rel_relkind(fc_relid)), fc_rv->relname);
}

/*
 * 在继承树上递归应用锁定表
 *
 * 这不会检查对子表执行锁定表的权限，因为到这里意味着用户有权限锁定
 * 父表，这已经足够。
 */
static void fc_LockTableRecurse(Oid fc_reloid, LOCKMODE fc_lockmode, bool fc_nowait)
{
	List	   *fc_children;
	ListCell   *fc_lc;

	fc_children = find_all_inheritors(fc_reloid, NoLock, NULL);

	foreach(fc_lc, fc_children)
	{
		Oid			fc_childreloid = lfirst_oid(fc_lc);

		/* 父表已经被锁定。 */
		if (fc_childreloid == fc_reloid)
			continue;

		if (!fc_nowait)
			LockRelationOid(fc_childreloid, fc_lockmode);
		else if (!ConditionalLockRelationOid(fc_childreloid, fc_lockmode))
		{
			/* 尝试通过名称抛出错误；关系可能已被删除... */
			char	   *fc_relname = get_rel_name(fc_childreloid);

			if (!fc_relname)
				continue;		/* 子表同时被删除，跳过它 */
			ereport(ERROR,
					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
					 errmsg("could not obtain lock on relation \"%s\"",
							fc_relname)));
		}

		/*
		 * 即使我们获得了锁，子表也可能已经被同时删除。
		 * 如果是这样，我们可以跳过它。
		 */
		if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_childreloid)))
		{
			/* 释放无用的锁 */
			UnlockRelationOid(fc_childreloid, fc_lockmode);
			continue;
		}
	}
}

/*
 * 在视图上递归应用锁定表
 *
 * 在视图定义查询中出现的所有表和视图都以相同的锁模式递归锁定
 */

typedef struct
{
	LOCKMODE	lockmode;		/* 要使用的锁模式 */
	bool		nowait;			/* 不等待模式 */
	Oid			check_as_user;	/* 用户权限检查 */
	Oid			viewoid;		/* 要锁定的视图的OID */
	List	   *ancestor_views; /* 祖先视图的OID */
} LockViewRecurse_context;

static bool fc_LockViewRecurse_walker(Node *fc_node, LockViewRecurse_context *fc_context)
{
	if (fc_node == NULL)
		return false;

	if (IsA(fc_node, Query))
	{
		Query	   *fc_query = (Query *) fc_node;
		ListCell   *fc_rtable;

		foreach(fc_rtable, fc_query->rtable)
		{
			RangeTblEntry *fc_rte = lfirst(fc_rtable);
			AclResult	fc_aclresult;

			Oid			fc_relid = fc_rte->relid;
			char		fc_relkind = fc_rte->relkind;
			char	   *fc_relname = get_rel_name(fc_relid);

			/*
			 * 视图的rtable中的OLD和NEW占位符条目被跳过。
			 */
			if (fc_relid == fc_context->viewoid &&
				(strcmp(fc_rte->eref->aliasname, "old") == 0 ||
				 strcmp(fc_rte->eref->aliasname, "new") == 0))
				continue;

			/* 目前，我们只允许锁定普通表或视图。 */
			if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE &&
				fc_relkind != RELKIND_VIEW)
				continue;

			/*
			 * 我们可能正在处理一个自引用视图。如果是这样，我们
			 * 可以停止递归，因为我们已经锁定它。
			 */
			if (list_member_oid(fc_context->ancestor_views, fc_relid))
				continue;

			/*
			 * 以指定用户的身份检查权限。这将是
			 * 视图所有者或当前用户。
			 */
			fc_aclresult = fc_LockTableAclCheck(fc_relid, fc_context->lockmode,
										  fc_context->check_as_user);
			if (fc_aclresult != ACLCHECK_OK)
				aclcheck_error(fc_aclresult, get_relkind_objtype(fc_relkind), fc_relname);

			/* 我们有足够的权限锁定该关系；执行锁定。 */
			if (!fc_context->nowait)
				LockRelationOid(fc_relid, fc_context->lockmode);
			else if (!ConditionalLockRelationOid(fc_relid, fc_context->lockmode))
				ereport(ERROR,
						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
						 errmsg("could not obtain lock on relation \"%s\"",
								fc_relname)));

			if (fc_relkind == RELKIND_VIEW)
				fc_LockViewRecurse(fc_relid, fc_context->lockmode, fc_context->nowait,
								fc_context->ancestor_views);
			else if (fc_rte->inh)
				fc_LockTableRecurse(fc_relid, fc_context->lockmode, fc_context->nowait);
		}

		return query_tree_walker(fc_query,
								 fc_LockViewRecurse_walker,
								 fc_context,
								 QTW_IGNORE_JOINALIASES);
	}

	return expression_tree_walker(fc_node,
								  fc_LockViewRecurse_walker,
								  fc_context);
}

static void fc_LockViewRecurse(Oid fc_reloid, LOCKMODE fc_lockmode, bool fc_nowait,
				List *fc_ancestor_views)
{
	LockViewRecurse_context fc_context;
	Relation	fc_view;
	Query	   *fc_viewquery;

	/* 调用者已经锁定了视图 */
	fc_view = table_open(fc_reloid, NoLock);
	fc_viewquery = get_view_query(fc_view);

	/*
	 * 如果视图具有security_invoker属性，则以
	 * 当前用户身份检查权限。否则，以视图所有者身份检查权限。
	 */
	fc_context.lockmode = fc_lockmode;
	fc_context.nowait = fc_nowait;
	if (RelationHasSecurityInvoker(fc_view))
		fc_context.check_as_user = GetUserId();
	else
		fc_context.check_as_user = fc_view->rd_rel->relowner;
	fc_context.viewoid = fc_reloid;
	fc_context.ancestor_views = lappend_oid(fc_ancestor_views, fc_reloid);

	fc_LockViewRecurse_walker((Node *) fc_viewquery, &fc_context);

	fc_context.ancestor_views = list_delete_last(fc_context.ancestor_views);

	table_close(fc_view, NoLock);
}

/*
 * 检查当前用户是否被允许锁定该关系。
 */
static AclResult
fc_LockTableAclCheck(Oid fc_reloid, LOCKMODE fc_lockmode, Oid fc_userid)
{
	AclResult	fc_aclresult;
	AclMode		fc_aclmask;

	/* 验证足够的权限 */
	if (fc_lockmode == AccessShareLock)
		fc_aclmask = ACL_SELECT;
	else if (fc_lockmode == RowExclusiveLock)
		fc_aclmask = ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
	else
		fc_aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;

	fc_aclresult = pg_class_aclcheck(fc_reloid, fc_userid, fc_aclmask);

	return fc_aclresult;
}
